package no.uib.hplims.views.orders;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import no.uib.hplims.Controller;
import no.uib.hplims.MyVaadinApplication;
import no.uib.hplims.jobs.CallbackInterface;
import no.uib.hplims.models.MyOrder;
import no.uib.hplims.models.Peptide;
import no.uib.hplims.models.Species;
import no.uib.hplims.models.peptideinformation.PeptideInformation;
import no.uib.hplims.tools.ExcelReader;
import no.uib.hplims.tools.ExportOrder;
import no.uib.hplims.tools.FileDownloadResource;
import no.uib.hplims.tools.Icons;
import no.uib.hplims.tools.LabelCreator;
import no.uib.hplims.tools.columngenerators.NumberOfBlastHitsColumnGenerator;
import no.uib.hplims.tools.columngenerators.SequenceModificationColumnGenerator;
import no.uib.hplims.tools.columngenerators.UniProtAccessionColumnGenerator;
import no.uib.hplims.tools.events.BlastSearchCompletedEvent;
import no.uib.hplims.tools.events.BlastSearchCompletedListener;
import no.uib.hplims.tools.events.BlastSearchStartedEvent;
import no.uib.hplims.tools.events.BlastSearchStartedListener;
import no.uib.hplims.tools.events.PeptideCreatedEvent;
import no.uib.hplims.tools.events.PeptideCreatedListener;
import no.uib.hplims.tools.events.PeptideUpdatedEvent;
import no.uib.hplims.tools.events.PeptideUpdatedListener;
import no.uib.hplims.tools.events.UpdatePeptideInformationEvent;
import no.uib.hplims.tools.events.UpdatePeptideInformationListener;
import no.uib.hplims.tools.validators.SequenceValidator;
import no.uib.hplims.views.experiments.AddToExperimentWindow;
import no.uib.hplims.views.peptides.PeptideInformationView;

import org.vaadin.appfoundation.persistence.facade.FacadeFactory;

import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.event.ItemClickEvent;
import com.vaadin.event.ItemClickEvent.ItemClickListener;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.ui.AbstractSelect.MultiSelectMode;
import com.vaadin.ui.AbstractSelect.NewItemHandler;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.Embedded;
import com.vaadin.ui.Form;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.OptionGroup;
import com.vaadin.ui.ProgressIndicator;
import com.vaadin.ui.Table;
import com.vaadin.ui.Table.ColumnGenerator;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.TextField;
import com.vaadin.ui.Upload;
import com.vaadin.ui.Upload.FailedEvent;
import com.vaadin.ui.Upload.SucceededEvent;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Window.CloseEvent;
import com.vaadin.ui.Window.Notification;
import com.vaadin.ui.themes.BaseTheme;
import com.vaadin.ui.themes.Runo;

/**
 * 
 * @author Administrator
 */
public class OrderEditor extends VerticalLayout implements
		Button.ClickListener, CallbackInterface, Upload.Receiver,
		Upload.FailedListener, Upload.SucceededListener {

	private static final long serialVersionUID = 6554950645316525755L;

	/**
	 * The order currently being edited
	 */
	private MyOrder order = null;

	private HorizontalLayout hl = null;
	private Label header = null;
	private TextField headerField = null;
	private Button editNameButton = null;
	private Label numberOfItemsLabel = null;

	private Controller controller = null;

	private Table peptideTable = null;
	private BeanItemContainer<Peptide> peptideContainer = null;

	private VerticalLayout peptideInformationArea = null;

	private RuleCustomizer ruleCustomizerWindow = null;
	private AddSequenceWindow addSequenceWindow = null;

	private VerticalLayout middleLayout = null;
	private VerticalLayout informationLayout = null;
	private Label salesOrderCodeLabel = null;
	private HorizontalLayout noBlastHitsNotificationLayout = null;
	private Label noBlastHitsInfo = new Label();
	private Button reBlastAllButton = new Button("Reblast?");

	private Form form = null;
	private TextArea peptideSequenceField = null;
	private NativeSelect enzymeSelect = null;
	private ComboBox speciesSelect = null;
	private HorizontalLayout progressBarLayout = null;
	private ProgressIndicator progressBar = null;
	private Label percentageIndicator = null;
	private VerticalLayout errorDisplayLayout = null;

	private VerticalLayout uploadLayout = null;
	private Upload upload = null;
	private File file = null;

	// Toolbar buttons
	private Button openAddWindowButton = null;
	private Button importFromFileButton = null;
	private Button exportToFileButton = null;
	private Button exportLabelsButton = null;

	// Buttons in form
	private Button submitButton = null;
	private Button resetFormButton = null;
	private Button customizeRulesButton = null;
	private OptionGroup sequenceInputType = null;

	// Buttons for table edit
	private Button editButton = null;
	private Button refreshButton = null;
	private Button removeItemButton = null;
	private Button modificationSettingsButton = null;
	private Button addToExperimentButton = null;

	private OptionGroup modificationsSelection = null;
	private Collection<?> selectedModifications = null;
	private SequenceModificationColumnGenerator sequenceModificationColumn = null;

	private final ArrayList<Object> visibleColumnIds = new ArrayList<Object>();
	private final ArrayList<String> visibleColumnLabels = new ArrayList<String>();

	public OrderEditor(MyOrder order) {
		this.order = order;
		setCaption(order.getOrderTitle());
		setMargin(true, true, false, true);
		setIcon(Icons.orderEditorIcon16);
	}

	@Override
	public void attach() {
		super.attach();

		initEditor();
	}

	private Controller getController() {
		// if controller hasn't been initialized, get application and
		// initialize it.
		if (controller == null) {
			MyVaadinApplication app = MyVaadinApplication.getInstance();
			controller = app.getController();

			// Add listener to controller's BlastTool
			controller.getBlastTool().addListener(peptideCreatedListener);
			controller.getBlastTool().addListener(blastSearchStartedListener);
			controller.getBlastTool().addListener(blastSearchCompletedListener);
			controller.getBlastTool().addListener(peptideUpdatedListener);
		}
		return controller;
	}

	private HorizontalLayout createMainToolbar() {
		HorizontalLayout toolbar = new HorizontalLayout();

		openAddWindowButton = new Button("Add");
		openAddWindowButton.setIcon(Icons.newItemIcon16);
		openAddWindowButton.addListener(this);
		openAddWindowButton.setEnabled(!order.isFinalized());

		importFromFileButton = new Button("Import");
		importFromFileButton.addListener(this);

		exportToFileButton = new Button("Export");
		exportToFileButton.addListener(this);

		exportLabelsButton = new Button("Labels");
		exportLabelsButton.addListener(this);
		exportLabelsButton.setEnabled(order.isFinalized());

		toolbar.addComponent(openAddWindowButton);
		toolbar.addComponent(importFromFileButton);
		toolbar.addComponent(exportToFileButton);
		toolbar.addComponent(exportLabelsButton);

		return toolbar;
	}

	private void initEditor() {
		if (hl == null) {
			/*
			 * Add toolbar to top of every order.
			 */
			addComponent(createMainToolbar());

			/*
			 * Create and add header.
			 */
			hl = new HorizontalLayout();
			hl.setSpacing(true);
			header = new Label(order.getOrderTitle());
			header.setStyleName(Runo.LABEL_H2);
			hl.addComponent(header);

			editNameButton = new Button("Edit");
			editNameButton.addStyleName(BaseTheme.BUTTON_LINK);
			editNameButton.addListener(this);
			hl.addComponent(editNameButton);
			hl.setComponentAlignment(editNameButton, Alignment.MIDDLE_CENTER);
			addComponent(hl);

			// Add layout for upload
			middleLayout = new VerticalLayout();
			addComponent(middleLayout);

			// add layout with order information
			informationLayout = new VerticalLayout();
			informationLayout.setSpacing(true);
			informationLayout.setMargin(false, false, true, false);
			salesOrderCodeLabel = new Label();
			salesOrderCodeLabel.setVisible(false);
			informationLayout.addComponent(salesOrderCodeLabel);
			middleLayout.addComponent(informationLayout);

			informationLayout.addComponent(getNoBlastHitsNotificationLayout());

			updateSalesOrderCodeLabel();

			/*
			 * Add toolbar for editing table.
			 */
			addComponent(createEditToolbar());

			/*
			 * Add table to Layout
			 */
			addComponent(getPeptideTable());
		}

		openAddDialogOnEmptyOrder();
	}

	private void openAddDialogOnEmptyOrder() {
		if (peptideContainer != null) {
			if (peptideContainer.size() == 0) {
				addButtonClick();
			}
		}
	}

	private Table getPeptideTable() {
		if (peptideTable == null) {
			// Create table
			peptideTable = new Table();
			peptideTable.setStyleName(Runo.TABLE_SMALL);

			// Adding ItemClickListener to handle clicks
			peptideTable.addListener(new ItemClickListener() {
				private static final long serialVersionUID = 875928070362720544L;

				// Handle click
				public void itemClick(ItemClickEvent event) {

					if (controller == null) {
						getController();
					}

					if (peptideInformationArea == null) {
						peptideInformationArea = new PeptideInformationView(
								(Peptide) event.getItemId(),
								updatePeptideInformationListener);
						addComponent(peptideInformationArea);
					} else {
						PeptideInformationView piv = new PeptideInformationView(
								(Peptide) event.getItemId(),
								updatePeptideInformationListener);
						replaceComponent(peptideInformationArea, piv);
						peptideInformationArea = piv;
					}

				}
			});

			// Enable/disable removeItemButton, depending on if there are items
			// selected
			peptideTable.addListener(new Property.ValueChangeListener() {
				private static final long serialVersionUID = 66492580135524046L;

				public void valueChange(ValueChangeEvent event) {
					Collection<?> items = (Collection<?>) event.getProperty()
							.getValue();
					removeItemButton.setEnabled(items.size() > 0);
					addToExperimentButton.setEnabled(items.size() > 0);
				}
			});

			// Table size
			peptideTable.setSizeFull();
			peptideTable.setPageLength(14);

			// Set up columns
			visibleColumnIds.add("uniProtAccession");
			visibleColumnIds.add("peptideName");
			visibleColumnIds.add("peptideSequence");
			visibleColumnIds.add("sequenceLength");
			visibleColumnIds.add("numBlastHits");
//			visibleColumnIds.add("prevOrder");

			// Add column labels
			visibleColumnLabels.add("Accession #");
			visibleColumnLabels.add("Peptide name");
			visibleColumnLabels.add("Sequence");
			visibleColumnLabels.add("Length");
			visibleColumnLabels.add("BLAST hits");
//			visibleColumnLabels.add("Times ordered");

			// Display UniProt Accession number as a link in table
			peptideTable.addGeneratedColumn("uniProtAccession",
					new UniProtAccessionColumnGenerator());

			sequenceModificationColumn = new SequenceModificationColumnGenerator(
					selectedModifications);
			// Mark known modifications in the peptide sequence red.
			peptideTable.addGeneratedColumn("peptideSequence",
					sequenceModificationColumn);

			// Display information about how many times a peptide has been
			// ordered previously (CURRENTLY NOT WORKING AS INTENDED)
//			peptideTable.addGeneratedColumn("prevOrder",
//					new TimesOrderedColumnGenerator());
//			peptideTable.setColumnAlignment("prevOrder", Table.ALIGN_CENTER);
//			peptideTable.setColumnExpandRatio("prevOrder", 0);
			
			peptideTable.addGeneratedColumn("numBlastHits", new NumberOfBlastHitsColumnGenerator());

			// Add container data source
			peptideTable.setContainerDataSource(getPeptideContainer());

			// Check peptide container to see if there are any peptides in the
			// order
			// which didn't return a BLAST hit.
			updateNoBlastHitsNotification();

			// Sort the columns and make the headers nicer
			if (!order.isFinalized()) {
				peptideTable.setVisibleColumns(visibleColumnIds.toArray());
				peptideTable.setColumnHeaders(visibleColumnLabels
						.toArray(new String[0]));
			} else {
				showFieldsWhenFinalized();
			}
			// Set table properties
			peptideTable.setEditable(false);
			peptideTable.setMultiSelect(true);
			peptideTable.setMultiSelectMode(MultiSelectMode.DEFAULT);
			peptideTable.setSelectable(true);
			peptideTable.setImmediate(true);
			peptideTable.setWriteThrough(true);

			/*
			 * Minimize width of certain columns to make as much space as
			 * possible available for the rest.
			 */
			// peptideTable.setColumnWidth("peptideSequence", 200);
			peptideTable.setColumnWidth("uniProtAccession", 85);
			peptideTable.setColumnWidth("numBlastHits", 75);
			peptideTable.setColumnWidth("sequenceLength", 65);

			// Align columns
			peptideTable.setColumnAlignment("uniProtAccession",
					Table.ALIGN_CENTER);
			peptideTable.setColumnAlignment("numBlastHits", Table.ALIGN_CENTER);
			peptideTable.setColumnAlignment("sequenceLength",
					Table.ALIGN_CENTER);

			// turn on column reordering and collapsing
			peptideTable.setColumnReorderingAllowed(true);
			peptideTable.setColumnCollapsingAllowed(true);
		}
		return peptideTable;
	}

	private BeanItemContainer<Peptide> getPeptideContainer() {
		if (peptideContainer == null) {
			peptideContainer = new BeanItemContainer<Peptide>(Peptide.class);
			String query = "SELECT p FROM Peptide AS p WHERE p.belongsToOrder=:order";
			Map<String, Object> parameters = new HashMap<String, Object>();
			parameters.put("order", this.order);
			List<Peptide> list = FacadeFactory.getFacade().list(query,
					parameters);
			peptideContainer.addAll(list);
		}
		numberOfItemsLabel.setValue("Items: " + peptideContainer.size());

		return peptideContainer;
	}

	private HorizontalLayout getProgressBar() {
		if (progressBarLayout == null) {
			progressBarLayout = new HorizontalLayout();
			progressBarLayout.setVisible(false);
			progressBarLayout.setSpacing(true);
			progressBarLayout.setMargin(false, false, false, true);
			progressBarLayout.setImmediate(true);

			Label label = new Label("BLAST progress:");
			progressBarLayout.addComponent(label);

			progressBar = new ProgressIndicator();
			progressBar.setIndeterminate(false);
			progressBar.setEnabled(true);
			progressBar.setValue(0f);

			progressBarLayout.addComponent(progressBar);

			percentageIndicator = new Label("0%");
			progressBarLayout.addComponent(percentageIndicator);

			progressBarLayout.setComponentAlignment(label,
					Alignment.MIDDLE_LEFT);
			progressBarLayout.setComponentAlignment(progressBar,
					Alignment.MIDDLE_LEFT);
			progressBarLayout.setComponentAlignment(percentageIndicator,
					Alignment.MIDDLE_LEFT);
		}
		return progressBarLayout;
	}

	public Form getForm() {
		return createForm();
	}

	private Form createForm() {
		if (form == null) {
			// Create form
			form = new Form();
			form.setWidth("500px");
			form.setImmediate(true);
			form.setCaption("Add Peptides");
			form.setDescription("Enter one or more peptide sequences to validate and add to order.");

			// form.addField("sequenceInputType", sequenceInputType);

			// Create field and add to form
			peptideSequenceField = new TextArea();
			peptideSequenceField
					.setInputPrompt("Type or paste peptide sequences - one per line");
			peptideSequenceField.setRequired(true);
			peptideSequenceField
					.setRequiredError("Enter at least one peptide sequence");
			peptideSequenceField.setRows(15);
			peptideSequenceField.setSizeFull();
			peptideSequenceField.addStyleName("noResizeTextArea");

			form.addField("sequenceField", peptideSequenceField);

			sequenceInputType = new OptionGroup();
			sequenceInputType.addListener(new Property.ValueChangeListener() {
				private static final long serialVersionUID = -2079457714913663684L;

				public void valueChange(ValueChangeEvent event) {
					if (event.getProperty().getValue() != "Peptide sequences") {
						if (enzymeSelect != null) {
							enzymeSelect.setEnabled(true);
						}
					} else {
						if (enzymeSelect != null) {
							enzymeSelect.setEnabled(false);
						}
					}
				}
			});
			sequenceInputType.setImmediate(true);
			sequenceInputType.addItem("Peptide sequences");
			sequenceInputType.addItem("Protein sequences");
			sequenceInputType.addItem("UniProt Accession numbers");

			form.addField("sequenceInputType", sequenceInputType);

			// Add enzyme selection list
			List<String> enzymeNames = getController().getEnzymeNames();
			enzymeSelect = new NativeSelect("Enzyme");
			enzymeSelect.setNullSelectionAllowed(false);
			for (String name : enzymeNames) {
				enzymeSelect.addItem(name);
			}
			// Select default enzyme
			enzymeSelect.select("Trypsin");

			form.addField("enzymeSelect", enzymeSelect);

			form.addField("speciesSelect", getSpeciesSelect());

			// Select default input type
			sequenceInputType.select("Peptide sequences");

			// Add a button bar in the form footer.
			HorizontalLayout buttonBar = new HorizontalLayout();
			buttonBar.setHeight("30px");
			buttonBar.setSpacing(true);
			form.getFooter().addComponent(buttonBar);

			// Add Ok and Reset buttons for the form.
			submitButton = new Button("Submit");
			submitButton.addListener(this);
			submitButton.addStyleName(Runo.BUTTON_DEFAULT);
			buttonBar.addComponent(submitButton);
			resetFormButton = new Button("Reset");
			resetFormButton.addListener(this);
			buttonBar.addComponent(resetFormButton);
			customizeRulesButton = new Button("Select Rules");
			customizeRulesButton.addListener(this);
			buttonBar.addComponent(customizeRulesButton);
		}
		return form;
	}

	private ComboBox getSpeciesSelect() {
		// Get list of species
		List<Species> species = FacadeFactory.getFacade().list(Species.class);

		speciesSelect = new ComboBox("Species");
		speciesSelect.setNullSelectionAllowed(false);
		speciesSelect.setNewItemsAllowed(true);
		speciesSelect.setNewItemHandler(new NewItemHandler() {
			private static final long serialVersionUID = 6768004145714047498L;

			public void addNewItem(String newItemCaption) {
				if (!speciesSelect.containsId(newItemCaption)) {
					// Save species to DB
					FacadeFactory.getFacade()
							.store(new Species(newItemCaption));
					// Add species to UI
					speciesSelect.addItem(newItemCaption);
					// Select the newly created species
					speciesSelect.setValue(newItemCaption);
					// Notify user
					getWindow().showNotification("Added species:",
							newItemCaption + " was added.",
							Notification.TYPE_TRAY_NOTIFICATION);
				}
			}
		});

		for (Species s : species) {
			speciesSelect.addItem(s.getName());
		}
		// Select default species
		speciesSelect.select("Homo sapiens");

		return speciesSelect;
	}

	protected VerticalLayout getErrorDisplayer() {
		if (errorDisplayLayout == null) {
			errorDisplayLayout = new VerticalLayout();
			errorDisplayLayout.setSpacing(true);
			errorDisplayLayout.setVisible(false);
		}
		return errorDisplayLayout;
	}

	protected void displayErrorMessage(
			final HashMap<String, List<String>> invalidSequences) {
		if (errorDisplayLayout == null) {
			errorDisplayLayout = new VerticalLayout();
		}
		errorDisplayLayout.removeAllComponents();

		Label errorHead = new Label("Errors:");
		errorHead.setStyleName(Runo.LABEL_H2);
		errorHead
				.setDescription("Please check your sequences for errors, or deactivate the corresponding rule.");
		errorDisplayLayout.addComponent(errorHead);

		for (final String sequence : invalidSequences.keySet()) {
			final VerticalLayout seqLayout = new VerticalLayout();
			final HorizontalLayout seqHeadLayout = new HorizontalLayout();
			seqHeadLayout.setSpacing(true);

			final Label seqLabel = new Label(sequence);
			seqLabel.setImmediate(true);

			final TextField field = new TextField();
			field.setValue(seqLabel.getValue());
			field.setImmediate(true);

			Button editSequenceButton = new Button("Edit");
			editSequenceButton.setStyleName(Runo.BUTTON_SMALL);
			editSequenceButton.addListener(new Button.ClickListener() {
				private static final long serialVersionUID = -8293508450258663635L;

				public void buttonClick(ClickEvent event) {

					Button btn = event.getButton();
					if (btn.getCaption() == "Edit") {
						btn.setCaption("OK");
						seqHeadLayout.replaceComponent(seqLabel, field);
					} else {
						btn.setCaption("Edit");

						seqLabel.setValue(field.getValue());
						seqHeadLayout.replaceComponent(field, seqLabel);
						List<String> errors = invalidSequences.remove(sequence);
						String newSequence = (String) seqLabel.getValue();
						invalidSequences.put(newSequence, errors);

						// Rebuild content of peptideSequenceField
						String newValue = "";
						for (String key : invalidSequences.keySet()) {
							newValue += key + "\n";
						}

						// Re-add only the remaining invalid sequences to
						// peptideSequenceField
						peptideSequenceField.setValue(newValue);

						// Simulate click on submitButton to revalidate
						// sequences after edit
						submitButtonClick();
					}

				}
			});
			Button deleteButton = new Button("Delete");
			deleteButton.setStyleName(Runo.BUTTON_SMALL);
			deleteButton.addListener(new Button.ClickListener() {
				private static final long serialVersionUID = -7833518674097621606L;

				public void buttonClick(ClickEvent event) {
					// Delete sequence from invalidSequence collection
					invalidSequences.remove(sequence);
					// Don't display the sequence and its errors anymore
					errorDisplayLayout.removeComponent(seqLayout);
					if (invalidSequences.size() < 1) {
						errorDisplayLayout.removeAllComponents();
					}

					// Rebuild content of peptideSequenceField
					String newValue = "";
					for (String key : invalidSequences.keySet()) {
						newValue += key + "\n";
					}

					// Re-add only the remaining invalid sequences to
					// peptideSequenceField
					peptideSequenceField.setValue(newValue);
				}
			});

			seqHeadLayout.addComponent(seqLabel);
			seqHeadLayout.addComponent(editSequenceButton);
			seqHeadLayout.addComponent(deleteButton);

			seqLayout.addComponent(seqHeadLayout);

			errorDisplayLayout.addComponent(seqLayout);

			for (String error : invalidSequences.get(sequence)) {
				Label errorLabel = new Label();
				errorLabel.setCaption(error);
				errorLabel.setIcon(Icons.errorIcon16);
				seqLayout.addComponent(errorLabel);
			}
		}

		errorDisplayLayout.setVisible(true);
	}

	private void removeErrorMessage() {
		errorDisplayLayout.setVisible(false);
	}

	public void buttonClick(ClickEvent event) {
		if (event.getButton() == openAddWindowButton) {

			addButtonClick();

		} else if (event.getButton().equals(submitButton)) {

			submitButtonClick();

		} else if (event.getButton().equals(importFromFileButton)) {

			importButtonClick();

		} else if (event.getButton().equals(exportToFileButton)) {

			// Create export object
			ExportOrder e = new ExportOrder();
			// Create file
			File f = e.createFile(order);
			// Get application
			MyVaadinApplication app = MyVaadinApplication.getInstance();
			// Make file available for download
			event.getButton().getWindow()
					.open(new FileDownloadResource(f, app));

		} else if (event.getButton() == exportLabelsButton) {

			LabelCreator lc = new LabelCreator();
			// Create file
			File f = lc.createFile(this.peptideContainer.getItemIds());
			// Get application
			MyVaadinApplication app = MyVaadinApplication.getInstance();
			// Make file available for download
			event.getButton().getWindow()
					.open(new FileDownloadResource(f, app));

		} else if (event.getButton().equals(resetFormButton)) {

			removeErrorMessage();
			peptideSequenceField.setValue("");

		} else if (event.getButton() == customizeRulesButton) {

			if (ruleCustomizerWindow == null) {
				ruleCustomizerWindow = new RuleCustomizer(getController()
						.getRf());
				ruleCustomizerWindow
						.setCaption("Select Sequence Validation Rules");
			}
			getWindow().addWindow(ruleCustomizerWindow);

		} else if (event.getButton() == refreshButton) {

			refreshEntityContainer();

		} else if (event.getButton() == removeItemButton) {

			final Object selection = peptideTable.getValue();
			if (selection == null) {
				return;
			}
			if (selection instanceof Peptide) {
				peptideTable.unselect(selection);
				FacadeFactory.getFacade().delete((Peptide) selection);
				peptideContainer.removeItem(selection);
				order.removePeptide((Peptide) selection);
				FacadeFactory.getFacade().store(order);
			}
			if (selection instanceof Collection) {
				final Collection selectionIndexes = (Collection) selection;
				for (final Object selectedIndex : selectionIndexes) {
					peptideTable.unselect(selectedIndex);
					FacadeFactory.getFacade().delete((Peptide) selectedIndex);
					peptideContainer.removeItem(selectedIndex);

					order.removePeptide((Peptide) selectedIndex);
					FacadeFactory.getFacade().store(order);
				}
			}
			updateNumberOfItemsLabel();

		} else if (event.getButton() == modificationSettingsButton) {

			modificationsButtonClick();

		} else if (event.getButton() == addToExperimentButton) {

			addToExperimentButtonClick();

		} else if (event.getButton() == editNameButton) {

			editExperimentName();

		}
	}

	private void modificationsButtonClick() {
		if (modificationsSelection == null) {
			modificationsSelection = new OptionGroup("Select modifications");

			modificationsSelection.setMultiSelect(true);
			modificationsSelection.setVisible(false);
			modificationsSelection.setImmediate(true);

			// Add available types of modifications
			modificationsSelection.addItem("Modified residues");
			modificationsSelection.addItem("Variant");
			modificationsSelection.addItem("Site");
			// Select all by default
			modificationsSelection.select("Modified residues");
			modificationsSelection.select("Variant");
			modificationsSelection.select("Site");

			// Change which modifications are desired to view, and update
			// the table
			modificationsSelection
					.addListener(new Property.ValueChangeListener() {
						private static final long serialVersionUID = -81562996732247808L;

						public void valueChange(ValueChangeEvent event) {
							// Set selected modifications
							selectedModifications = (Collection<?>) event
									.getProperty().getValue();
							if (peptideTable != null) {
								// Remove previously displayed modifications
								peptideTable
										.removeGeneratedColumn("peptideSequence");
								sequenceModificationColumn
										.updateSelectedMods(selectedModifications);
								// Update with new preferences
								peptideTable.addGeneratedColumn(
										"peptideSequence",
										sequenceModificationColumn);
							}
						}
					});

			// Add to layout
			middleLayout.addComponent(modificationsSelection);
		}
		// Show/hide when button is clicked
		modificationsSelection.setVisible(!modificationsSelection.isVisible());
	}

	private void addToExperimentButtonClick() {
		final Object selection = peptideTable.getValue();
		Collection<Peptide> selectionIndexes = null;
		if (selection == null) {
			return;
		}
		if (selection instanceof Peptide) {
			selectionIndexes = new ArrayList<Peptide>();
			selectionIndexes.add((Peptide) selection);
		} else if (selection instanceof Collection) {
			selectionIndexes = (Collection<Peptide>) selection;
		}

		if (selectionIndexes != null) {
			Window window = new AddToExperimentWindow(selectionIndexes);

			getWindow().addWindow(window);
		}
	}

	private void importButtonClick() {
		if (middleLayout.getComponentIndex(getUploadLayout()) == -1) {
			middleLayout.addComponent(getUploadLayout());
		} else {
			middleLayout.removeComponent(getUploadLayout());
		}

	}

	private void refreshEntityContainer() {
		peptideTable.removeAllItems();
		peptideContainer = null;
		peptideTable.setContainerDataSource(getPeptideContainer());
		// Sort the columns and make the headers nicer
		peptideTable.setVisibleColumns(visibleColumnIds.toArray());
		peptideTable.setColumnHeaders(visibleColumnLabels
				.toArray(new String[0]));
	}

	private void addButtonClick() {
		// Disable button when clicked
		openAddWindowButton.setEnabled(false);

		if (addSequenceWindow == null) {
			addSequenceWindow = new AddSequenceWindow(this);
			addSequenceWindow.addListener(new Window.CloseListener() {

				private static final long serialVersionUID = 6698963268254227447L;

				public void windowClose(CloseEvent e) {
					openAddWindowButton.setEnabled(true);
				}
			});
		}
		getWindow().addWindow(addSequenceWindow);
	}

	protected void submitButtonClick() {
		/*
		 * Initialize the SequenceValidator for peptideSequenceField
		 */
		SequenceValidator sequenceValidator = null;
		if (peptideSequenceField.getValidators() == null) {
			// Create and add the validator if it hasn't already been added.
			sequenceValidator = new SequenceValidator(getController());
			peptideSequenceField.addValidator(sequenceValidator);
		} else {
			if (peptideSequenceField.getValidators().iterator().hasNext()) {
				sequenceValidator = (SequenceValidator) peptideSequenceField
						.getValidators().iterator().next();
			}
		}
		/*
		 * The input type in peptideSequenceField can be regular peptide
		 * sequences, or entire protein sequences in pure sequence or FASTA
		 * form, as well as UniProt Accession numbers. If input is one of the
		 * latter, the sequences will need in silico digestion before BLAST is
		 * performed on the resulting peptides.
		 */
		if (sequenceInputType.getValue() == "Protein sequences") {
			// For protein sequences, either in FASTA form or as pure sequences

			// Start in silico digestion
			ArrayList<String> peptides = inSilicoDigestion((String) peptideSequenceField
					.getValue());

			if (peptides != null) {
				String seqs = "";
				for (String s : peptides) {
					seqs += s + "\n";
				}
				/*
				 * Replace current content of peptideSequenceField with the in
				 * silico digested peptides
				 */
				peptideSequenceField.setValue(seqs);
			}

		} else if (sequenceInputType.getValue() == "UniProt Accession numbers") {
			// For UniProt Accession numbers

			/*
			 * Find protein sequences for a list of UniProt Accession numbers
			 */
			String seqs = getController().lookUpUniProtAccessions(
					(String) peptideSequenceField.getValue());
			// Start in silico digestion
			ArrayList<String> peptides = inSilicoDigestion(seqs);
			if (peptides != null) {
				seqs = "";
				for (String s : peptides) {
					seqs += s + "\n";
				}

				peptideSequenceField.setValue(seqs);
			}
		}

		/*
		 * Validate peptide sequences that now are in the peptideSequenceField.
		 * BLAST is automatically started for sequences validated with the
		 * currently active rules.
		 */

		if (peptideSequenceField.isValid()) {
			System.out.println("Valid sequences");
			removeErrorMessage();

			performBlastSearch(sequenceValidator.getValidSequences(),
					(String) speciesSelect.getValue());

			peptideSequenceField.setValue("");

			if (addSequenceWindow != null) {
				getWindow().removeWindow(addSequenceWindow);
			}

		} else {
			// Start BLAST on valid sequences, if any
			performBlastSearch(sequenceValidator.getValidSequences(),
					(String) speciesSelect.getValue());

			if (sequenceInputType.getValue() == "Peptide sequences") {
				// Get the invalid sequences
				HashMap<String, List<String>> invalid = sequenceValidator
						.getInvalidSequences();
				String newValue = "";
				for (String key : invalid.keySet()) {
					newValue += key + "\n";
				}
				// Re-add only the invalid sequences to peptideSequenceField
				peptideSequenceField.setValue(newValue);

				// Display error messages for the invalid sequences, if any
				if (sequenceValidator.getInvalidSequences().size() > 0) {
					displayErrorMessage(sequenceValidator.getInvalidSequences());
				}
			} else {
				/*
				 * Remove all invalid sequences from input after in silico
				 * digestion
				 */
				peptideSequenceField.setValue("");
				/*
				 * Reset input type to peptide sequences, as the previous
				 * content of peptideSequenceField has been replaced with in
				 * silico digested peptide sequences
				 */
				sequenceInputType.select("Peptide sequences");

				if (addSequenceWindow != null) {
					getWindow().removeWindow(addSequenceWindow);
				}
			}
		}
	}

	/**
	 * Starts in silico digestion of protein sequences.
	 * 
	 * @param input
	 *            String of sequences, separated by linebreak (\n).
	 * @return List of peptides after in silico digestion
	 */
	private ArrayList<String> inSilicoDigestion(String input) {
		ArrayList<String> allPeptides = null;
		String enzymeName = (String) enzymeSelect.getValue();

		// make sure string is all uppercase
		input = input.toUpperCase();

		if (input != null && input != "") {
			allPeptides = getController().startInSilicoDigestion(input,
					enzymeName);
		} else {
			allPeptides = new ArrayList<String>();
		}

		return allPeptides;
	}

	/**
	 * Start BLAST on valid sequences, if any
	 * 
	 * @param validSequences
	 *            List of valid sequences
	 */
	private void performBlastSearch(List<String> validSequences, String species) {
		if (validSequences.size() > 0) {
			// Send sequences, order and the controller to PeptideFactory
			getController().startBlastSearch(validSequences, order, species);
		}
	}

	private Component createEditToolbar() {
		HorizontalLayout layout = new HorizontalLayout();
		layout.setSizeFull();

		final HorizontalLayout buttonLayout = new HorizontalLayout();
		buttonLayout.addStyleName(Runo.PANEL_LIGHT);
		
		editButton = new Button("Edit");
		editButton.setIcon(Icons.editIcon16);
		editButton.addListener(editButtonListener);
		buttonLayout.addComponent(editButton);

		refreshButton = new Button("Refresh");
		refreshButton.setIcon(Icons.refreshIcon16);
		refreshButton.addListener(this);
		buttonLayout.addComponent(refreshButton);

		removeItemButton = new Button("Remove Row");
		removeItemButton.setIcon(Icons.removeIcon16);
		removeItemButton.addListener(this);
		removeItemButton.setEnabled(false);
		buttonLayout.addComponent(removeItemButton);

		modificationSettingsButton = new Button("Modifications");
		modificationSettingsButton.addListener(this);
		buttonLayout.addComponent(modificationSettingsButton);

		addToExperimentButton = new Button("Add to experiment");
		addToExperimentButton.addListener(this);
		addToExperimentButton.setEnabled(false);
		buttonLayout.addComponent(addToExperimentButton);

		buttonLayout.addComponent(getProgressBar());

		numberOfItemsLabel = new Label();
		numberOfItemsLabel.setSizeUndefined();

		layout.addComponent(buttonLayout);

		layout.addComponent(numberOfItemsLabel);
		layout.setComponentAlignment(numberOfItemsLabel, Alignment.BOTTOM_RIGHT);

		return layout;
	}

	public void showProgress() {
		updateProgressBar(100, 100);
		getProgressBar().setVisible(true);
	}

	public void updateProgressBar(int total, int remaining) {
		System.out.println("Total: " + total + "\n Unfinished: " + remaining);

		if (remaining == 0) {
			progressBar.setValue(1f);
		} else {
			float progress = (float) (total - remaining) / total;
			progressBar.setValue(progress);
			int prog = (int) (progress * 100);
			percentageIndicator.setValue(prog + "%");
		}
	}

	private void hideProgress() {
		System.out.println("Hide progress");
		getProgressBar().setVisible(false);
		System.out.println("Progressbar visible: "
				+ getProgressBar().isVisible());
	}

	private PeptideCreatedListener peptideCreatedListener = new PeptideCreatedListener() {

		public void eventOccurred(PeptideCreatedEvent event) {

			System.out.println("Peptide created event: ");
			Peptide p = event.getPeptide();
			System.out.println(p);

			if (p.getBelongsToOrder().getId().longValue() == order.getId()
					.longValue()) {
				System.out.println("Do we enter?");
				getPeptideContainer().addBean(p);
				System.out.println("Bean added");
				order.addPeptide(p);
				System.out.println("Added to order");
				FacadeFactory.getFacade().store(order);
				System.out.println("Save order");
				
				getController().refreshOrderContainer();

				updateNumberOfItemsLabel();
				updateNoBlastHitsNotification();

				int total = event.getTotal();
				int remaining = event.getRemaining();
				updateProgressBar(total, remaining);
				System.out.println("Updated progressbar");
			}
		}
	};

	public void returnResult(Object result) {
		if (result instanceof Peptide) {

			getPeptideContainer().addBean((Peptide) result);

			if (getPeptideContainer().size() % 4 == 0) {
				// Update order for every 4th peptide
				// this.order.setNumberOfPeptidesInOrder(getPeptideContainer()
				// .size());
				FacadeFactory.getFacade().store(order);
				getController().refreshOrderContainer();
			}

		} else if (result instanceof String) {
			if ((String) result == "Blast search complete") {

				hideProgress();

				// Update order
				// this.order.setNumberOfPeptidesInOrder(getPeptideContainer()
				// .size());
				FacadeFactory.getFacade().store(order);
				getController().refreshOrderContainer();

			} else if ((String) result == "Blast search incomplete") {

				// showProgress();

			} else if ((String) result == "Reblast complete") {

				hideProgress();
				refreshEntityContainer();

			}
			updateNumberOfItemsLabel();
		}
	}

	private void updateNumberOfItemsLabel() {
		numberOfItemsLabel.setValue("Items: " + order.getPeptideIDs().size());
	}

	public void refreshOrderEditor() {
		refreshEntityContainer();
	}

	private VerticalLayout getUploadLayout() {
		if (uploadLayout == null) {
			uploadLayout = new VerticalLayout();
			uploadLayout.setSpacing(true);
			uploadLayout.setMargin(true, false, true, false);

			upload = new Upload("Upload file", this);
			upload.setImmediate(false);
			upload.addListener((Upload.SucceededListener) this);
			upload.addListener((Upload.FailedListener) this);

			uploadLayout
					.addComponent(new Label(
							"Upload a file to import peptide information pertaining to this order"));
			uploadLayout.addComponent(upload);
		}
		return uploadLayout;
	}

	public void uploadSucceeded(SucceededEvent event) {
		// Log the upload on screen.
		getUploadLayout().addComponent(
				new Label("File " + event.getFilename() + " of type '"
						+ event.getMIMEType() + "' uploaded."));

		ExcelReader eReader = new ExcelReader();
		boolean success = eReader.startImport(order, file);
		if (success) {
			getUploadLayout().addComponent(new Label("Import OK"));
			finalizeOrder();
			uploadFinished();
		} else {
			getUploadLayout().addComponent(new Label("Import failed"));
		}

	}

	public void uploadFailed(FailedEvent event) {
		// Log the failure on screen.
		getUploadLayout().addComponent(
				new Label("Uploading " + event.getFilename() + " of type '"
						+ event.getMIMEType() + "' failed."));

	}

	public OutputStream receiveUpload(String filename, String mimeType) {
		FileOutputStream fos = null;
		file = new File(filename);
		try {
			fos = new FileOutputStream(file);
		} catch (final java.io.FileNotFoundException e) {
			// Error while opening the file. Not reported here.
			e.printStackTrace();
			return null;
		}
		return fos;
	}

	private void finalizeOrder() {
		// Change order's status
		order.setFinalized(true);
		FacadeFactory.getFacade().store(order);

		// Disable add-button when order is finalized
		openAddWindowButton.setEnabled(false);
		// Enable label-export
		exportLabelsButton.setEnabled(true);

		showFieldsWhenFinalized();

		// removes uploadlayout if it is present
		middleLayout.removeComponent(getUploadLayout());
	}

	private void showFieldsWhenFinalized() {
		visibleColumnIds.add("positionNumber");
		visibleColumnIds.add("wellID");
		visibleColumnIds.add("nTerminal");
		visibleColumnIds.add("cTerminal");
		visibleColumnIds.add("molecularWeight");
		visibleColumnIds.add("amountInMg");
		visibleColumnIds.add("quality");
		visibleColumnIds.add("optimized");

		visibleColumnLabels.add("Pos. #");
		visibleColumnLabels.add("Well ID");
		visibleColumnLabels.add("N-term");
		visibleColumnLabels.add("C-term");
		visibleColumnLabels.add("MW");
		visibleColumnLabels.add("Amount (mg)");
		visibleColumnLabels.add("Quality");
		visibleColumnLabels.add("Optimized");

		peptideTable.addGeneratedColumn("quality", new ColumnGenerator() {
			private static final long serialVersionUID = -4595195519808736721L;

			public Object generateCell(Table source, Object itemId,
					Object columnId) {
				Peptide p = (Peptide) itemId;
				PeptideInformation pi = FacadeFactory.getFacade().find(
						PeptideInformation.class, p.getPeptideInformationID());

				return new Label(pi.getQuality());
			}
		});

		peptideTable.addGeneratedColumn("optimized", new ColumnGenerator() {
			private static final long serialVersionUID = -4595195519808736721L;

			public Object generateCell(Table source, Object itemId,
					Object columnId) {
				Peptide p = (Peptide) itemId;
				PeptideInformation pi = FacadeFactory.getFacade().find(
						PeptideInformation.class, p.getPeptideInformationID());
				if (pi.isOptimized()) {
					Embedded image = new Embedded("", new ThemeResource(
							"../hplims/icons/check.png"));
					return image;
				} else {
					return new Label();
				}
			}
		});

		peptideTable.setVisibleColumns(visibleColumnIds.toArray());
		peptideTable.setColumnHeaders(visibleColumnLabels
				.toArray(new String[0]));
	}

	private void uploadFinished() {
		refreshEntityContainer();
		updateSalesOrderCodeLabel();
	}

	private void editExperimentName() {
		if (headerField == null) {
			headerField = new TextField();
			headerField.setValue((String) header.getValue());
			editNameButton.setCaption("OK");
			hl.replaceComponent(header, headerField);
		} else {
			if ((String) headerField.getValue() != "") {
				header.setValue((String) headerField.getValue());
				order.setOrderTitle((String) header.getValue());
				editNameButton.setCaption("Edit");
				hl.replaceComponent(headerField, header);
				MyVaadinApplication.getInstance().getController()
						.updateOrder(order);
			} else {
				hl.replaceComponent(headerField, header);
			}
			headerField = null;
		}
	}

	private void updateSalesOrderCodeLabel() {
		if (order.isFinalized()) {
			salesOrderCodeLabel.setValue("<strong>Sales Ordercode:</strong> "
					+ order.getSalesOrderCode());
			salesOrderCodeLabel.setContentMode(Label.CONTENT_XHTML);
			salesOrderCodeLabel.setVisible(true);
		} else {
			salesOrderCodeLabel.setValue("");
			salesOrderCodeLabel.setVisible(false);
		}
	}

	public MyOrder getOrder() {
		return order;
	}

	private BlastSearchStartedListener blastSearchStartedListener = new BlastSearchStartedListener() {

		public void eventOccurred(BlastSearchStartedEvent event) {
			// Display progressbar
			showProgress();
		}
	};

	private BlastSearchCompletedListener blastSearchCompletedListener = new BlastSearchCompletedListener() {

		public void eventOccurred(BlastSearchCompletedEvent event) {
			System.out.println("BlastSearchCompletedEvent");

			hideProgress();
		}
	};

	private PeptideUpdatedListener peptideUpdatedListener = new PeptideUpdatedListener() {

		public void eventOccurred(PeptideUpdatedEvent event) {
			ArrayList<Peptide> list = event.getPeptideList();

			for (Peptide p : list) {
				if (p.getBelongsToOrder().equals(order)) {
					refreshEntityContainer();
					updateNoBlastHitsNotification();
					// hideProgress();
					break;
				}
			}

		}
	};

	private UpdatePeptideInformationListener updatePeptideInformationListener = new UpdatePeptideInformationListener() {

		public void eventOccurred(UpdatePeptideInformationEvent event) {
			// TODO: Update only the one peptide returned from the event
			refreshEntityContainer();
		}
	};

	private HorizontalLayout getNoBlastHitsNotificationLayout() {
		if (noBlastHitsNotificationLayout == null) {
			noBlastHitsNotificationLayout = new HorizontalLayout();
			noBlastHitsNotificationLayout.setSpacing(true);
			noBlastHitsNotificationLayout.setStyleName("error");

			noBlastHitsInfo.setContentMode(Label.CONTENT_XHTML);
			noBlastHitsInfo.setSizeUndefined();
			noBlastHitsNotificationLayout.addComponent(noBlastHitsInfo);

			reBlastAllButton.setStyleName(Runo.BUTTON_LINK);
			reBlastAllButton.addListener(reBlastButtonClickListener);
			noBlastHitsNotificationLayout.addComponent(reBlastAllButton);
		}
		return noBlastHitsNotificationLayout;
	}

	private void updateNoBlastHitsNotification() {
		ArrayList<Peptide> peptidesWithoutHits = getPeptidesWithoutBlastHits();

		if (peptidesWithoutHits.size() > 1) {
			noBlastHitsInfo.setValue("There are " + peptidesWithoutHits.size()
					+ " peptides with no BLAST hits. ");

			noBlastHitsNotificationLayout.setVisible(true);
		} else if (peptidesWithoutHits.size() == 1) {
			noBlastHitsInfo.setValue("There is " + peptidesWithoutHits.size()
					+ " peptide with no BLAST hits. ");

			noBlastHitsNotificationLayout.setVisible(true);
		} else {
			noBlastHitsNotificationLayout.setVisible(false);
		}
	}

	private ArrayList<Peptide> getPeptidesWithoutBlastHits() {
		Collection<Peptide> peptides = peptideContainer.getItemIds();
		ArrayList<Peptide> peptidesWithoutHits = new ArrayList<Peptide>();
		if (peptides.size() > 0) {
			for (Peptide p : peptides) {
				if (p.getPeptideName().contains("No blast hit")) {
					peptidesWithoutHits.add(p);
				}
			}
		}
		return peptidesWithoutHits;
	}

	private ClickListener reBlastButtonClickListener = new ClickListener() {

		private static final long serialVersionUID = 2028572861074595680L;

		public void buttonClick(ClickEvent event) {
			ArrayList<Peptide> peptidesWithoutHits = getPeptidesWithoutBlastHits();
			ArrayList<String> sequences = new ArrayList<String>();

			for (Peptide p : peptidesWithoutHits) {
				sequences.add(p.getPeptideSequence());
			}

			String species = null;
			if ((sequences.size() >= 1)
					&& (sequences.size() == peptidesWithoutHits.size())) {
				species = peptidesWithoutHits.get(0).getSpecies();
				getController().resubmitForBlast(sequences, order, species);
			}
		}
	};
	
	private ClickListener editButtonListener = new ClickListener() {
		
		private static final long serialVersionUID = 1933564753779502898L;

		public void buttonClick(ClickEvent event) {
			Button btn = event.getButton();
			if (btn.getCaption().equals("Edit")){
				getPeptideTable().setEditable(true);
				btn.setCaption("Save");
				btn.setIcon(Icons.saveIcon16);
			} else if (btn.getCaption().equals("Save")) {
				getPeptideTable().setEditable(false);
				btn.setCaption("Edit");
				btn.setIcon(Icons.editIcon16);
				FacadeFactory.getFacade().storeAll(getPeptideContainer().getItemIds());
			}
		}
	};

}
